package net.sourceforge.fidocadj.primitives;

import java.io.*;
import java.util.*;

import net.sourceforge.fidocadj.dialogs.*;
import net.sourceforge.fidocadj.export.*;
import net.sourceforge.fidocadj.geom.*;
import net.sourceforge.fidocadj.globals.*;
import net.sourceforge.fidocadj.graphic.*;

/** Class to handle the PCB pad primitive.

    <pre>
    This file is part of FidoCadJ.

    FidoCadJ is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    FidoCadJ is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with FidoCadJ. If not,
    @see <a href=http://www.gnu.org/licenses/>http://www.gnu.org/licenses/</a>.

    Copyright 2007-2014 by Davide Bucci
    </pre>

    @author Davide Bucci
*/
public final class PrimitivePCBPad extends GraphicPrimitive
{
    private int rx;
    private int ry;
    private int sty;
    private int ri;

    // The radius of the rounded corner in logical units. This is hardcoded
    // here as it has been done for FidoCadJ, but one may consider let this
    // value to be changed by the user interactively
    private static final int CORNER_DIAMETER = 5;

    private boolean drawOnlyPads;

    // A PCB pad is defined by one points, plus text tags

    static final int N_POINTS=3;

    // Those are data which are kept for the fast redraw of this primitive.
    // Basically, they are calculated once and then used as much as possible
    // without having to calculate everything from scratch.
    private int x1, y1, rrx, rry, xa,ya, rox, roy, rix, riy;
    private int rrx2, rry2, rix2, riy2;

    /** Gets the number of control points used.
        @return the number of points used by the primitive
    */
    public int getControlPointNumber()
    {
        return N_POINTS;
    }

    /** Create a PCB pad
        @param x1 the x coordinate (logical unit).
        @param y1 the y coordinate (logical unit).
        @param wx the width of the pad
        @param wy the height of the pad
        @param radi the internal radius.
        @param st the style of the pad
        @param layer the layer to be used.
        @param f the name of the font for attached text.
        @param size the size of the font for attached text.
    */
    public PrimitivePCBPad(int x1, int y1, int wx, int wy, int radi, int st,
        int layer, String f, int size)
    {
        super();
        initPrimitive(-1, f, size);

        virtualPoint[0].x=x1;
        virtualPoint[0].y=y1;
        virtualPoint[getNameVirtualPointNumber()].x=x1+5;
        virtualPoint[getNameVirtualPointNumber()].y=y1+5;
        virtualPoint[getValueVirtualPointNumber()].x=x1+5;
        virtualPoint[getValueVirtualPointNumber()].y=y1+10;

        rx=wx;
        ry=wy;
        ri=radi;
        sty=st;

        setLayer(layer);
    }

    /** Constructor. Create an empty PCBPad object with all the sizes and
        dimensions set to zero.
        @param f the name of the font for attached text.
        @param size the size of the font for attached text.
    */
    public PrimitivePCBPad(String f, int size)
    {
        super();
        rx=0;
        ry=0;
        sty=0;
        ri=0;
        initPrimitive(-1, f, size);
    }

    /** Check if the holes should be drawn.
        @return true for a PCBpad.
    */
    @Override
    public boolean needsHoles()
    {
        return true;
    }

    /** Draw the graphic primitive on the given graphic context.
        @param g the graphic context in which the primitive should be drawn.
        @param coordSys the graphic coordinates system to be applied.
        @param layerV the layer description.
    */
    public void draw(GraphicsInterface g, MapCoordinates coordSys,
                              Vector layerV)
    {
        if(!selectLayer(g,layerV))
            return;
        drawText(g, coordSys, layerV, -1);

        if(changed) {
            changed=false;

            /* in the PCB pad primitive, the the virtual points represent
           the position of the pad to be drawn. */
            x1=virtualPoint[0].x;
            y1=virtualPoint[0].y;

            xa=coordSys.mapXi(x1,y1,false);
            ya=coordSys.mapYi(x1,y1,false);

            rrx=Math.abs(xa-coordSys.mapXi(x1+rx,y1+ry, false));
            rry=Math.abs(ya-coordSys.mapYi(x1+rx,y1+ry, false));
            rrx2 = rrx/2;
            rry2 = rry/2;

            coordSys.trackPoint(xa-rrx2,ya-rry2);
            coordSys.trackPoint(xa+rrx2,ya+rry2);

            rox=Math.abs(xa-coordSys.mapXi(x1+CORNER_DIAMETER,
                y1+CORNER_DIAMETER, false));
            roy=Math.abs(ya-coordSys.mapYi(x1+CORNER_DIAMETER,
                y1+CORNER_DIAMETER, false));

            rix=Math.abs(xa-coordSys.mapXi(x1+ri,y1+ri, false));
            riy=Math.abs(ya-coordSys.mapYi(x1+ri,y1+ri, false));

            rix2 = rix/2;
            riy2 = riy/2;
        }

        // Exit if the primitive is offscreen. This is a simplification, but
        // ensures that the primitive is correctly drawn when it is
        // partially visible.

        if(!g.hitClip(xa-rrx2,ya-rry2, rrx, rry))
            return;

        g.applyStroke(1, 0);

        if (drawOnlyPads) {
            g.setColor(g.getColor().white()); // Drill the hole
            g.fillOval(xa-rix2, ya-riy2,rix,riy);
        } else {
            switch(sty) {
                case 1:
                    // Rectangular pad
                    g.fillRect(xa-rrx2, ya-rry2,rrx,rry);
                    break;
                case 2:
                    // Rounded corner rectangular pad
                    g.fillRoundRect(xa-rrx2, ya-rry2,rrx,rry,rox,roy);
                    break;
                case 0: //NOPMD
                default:
                    // Oval Pad
                    g.fillOval(xa-rrx2, ya-rry2,rrx,rry);
                    break;
            }
        }
    }

    /** Parse a token array and store the graphic data for a given primitive
        Obviously, that routine should be called *after* having recognized
        that the called primitive is correct.
        That routine also sets the current layer.

        @param tokens the tokens to be processed. tokens[0] should be the
        command of the actual primitive.
        @param N the number of tokens present in the array.
        @throws IOException if the arguments are incorrect or the primitive
            is invalid.
    */
    public void parseTokens(String[] tokens, int N)
        throws IOException
    {
        changed=true;

        // assert it is the correct primitive

        if (tokens[0].equals("PA")) {   // PCB Area pad
            /* Example PA 752 50 15 15 4 1 1 */

            if (N<7)  {
                IOException E=new IOException("bad arguments on PA");
                throw E;
            }
            // Load the points in the virtual points associated to the
            // current primitive.

            int x1 = virtualPoint[0].x=Integer.parseInt(tokens[1]);
            int y1 = virtualPoint[0].y=Integer.parseInt(tokens[2]);
            virtualPoint[getNameVirtualPointNumber()].x=x1+5;
            virtualPoint[getNameVirtualPointNumber()].y=y1+5;
            virtualPoint[getValueVirtualPointNumber()].x=x1+5;
            virtualPoint[getValueVirtualPointNumber()].y=y1+10;
            rx=Integer.parseInt(tokens[3]);
            ry=Integer.parseInt(tokens[4]);
            ri=Integer.parseInt(tokens[5]);
            sty=Integer.parseInt(tokens[6]);

            if(N>7) parseLayer(tokens[7]);

        } else {
            IOException E=new IOException("PA: Invalid primitive:"+tokens[0]+
                                          " programming error?");
            throw E;
        }
    }

    /** Set the draw only pads flag.
        @param pd the value of the flag.
    */
    public void setDrawOnlyPads(boolean pd)
    {
        drawOnlyPads=pd;
    }

    /** Get the control parameters of the given primitive.
        @return a vector of ParameterDescription containing each control
                parameter.
                The first parameters should always be the virtual points.
    */
    public Vector<ParameterDescription> getControls()
    {
        Vector<ParameterDescription> v=super.getControls();
        ParameterDescription pd = new ParameterDescription();

        pd.parameter= Integer.valueOf(rx);
        pd.description=Globals.messages.getString("ctrl_x_radius");
        v.add(pd);
        pd = new ParameterDescription();
        pd.parameter= Integer.valueOf(ry);
        pd.description=Globals.messages.getString("ctrl_y_radius");
        v.add(pd);
        pd = new ParameterDescription();
        pd.parameter= Integer.valueOf(ri);
        pd.description=Globals.messages.getString("ctrl_internal_radius");
        v.add(pd);
        pd = new ParameterDescription();
        pd.parameter= Integer.valueOf(sty); // A list should be better
        pd.description=Globals.messages.getString("ctrl_pad_style");
        v.add(pd);

        return v;
    }

    /** Set the control parameters of the given primitive.
        This method is specular to getControls().
        @param v a vector of ParameterDescription containing each control
                parameter.
                The first parameters should always be the virtual points.
        @return the next index in v to be scanned (if needed) after the
            execution of this function.
    */
    public int setControls(Vector<ParameterDescription> v)
    {
        int i=super.setControls(v);
        ParameterDescription pd;

        pd=(ParameterDescription)v.get(i);
        ++i;
        // Check, just for sure...
        if (pd.parameter instanceof Integer)
            rx=((Integer)pd.parameter).intValue();
        else
            System.out.println("Warning: unexpected parameter!"+pd);

        pd=(ParameterDescription)v.get(i);
        ++i;
        // Check, just for sure...
        if (pd.parameter instanceof Integer)
            ry=((Integer)pd.parameter).intValue();
        else
            System.out.println("Warning: unexpected parameter!"+pd);

        pd=(ParameterDescription)v.get(i);
        ++i;
        // Check, just for sure...
        if (pd.parameter instanceof Integer)
            ri=((Integer)pd.parameter).intValue();
        else
            System.out.println("Warning: unexpected parameter!"+pd);
        pd=(ParameterDescription)v.get(i);
        ++i;
        // Check, just for sure...
        if (pd.parameter instanceof Integer)
            sty=((Integer)pd.parameter).intValue();
        else
            System.out.println("Warning: unexpected parameter!"+pd);
        return i;
    }

    /** Rotate the primitive. Here we just rotate 90° by 90° by swapping the
        x and y size of the pad
        @param bCounterClockWise specify if the rotation should be done
                counterclockwise.
        @param ix the x coordinate of the rotation point.
        @param iy the y coordinate of the rotation point.
    */
    public void rotatePrimitive(boolean bCounterClockWise, int ix, int iy)
    {
        super.rotatePrimitive(bCounterClockWise, ix, iy);
        int swap=rx;
        rx=ry;
        ry=swap;
    }

    /** Gets the distance (in primitive's coordinates space) between a
        given point and the primitive.
        When it is reasonable, the behaviour can be binary (polygons,
        ovals...). In other cases (lines, points), it can be proportional.
        @param px the x coordinate of the given point.
        @param py the y coordinate of the given point.
        @return the distance in logical units.
    */
    public int getDistanceToPoint(int px, int py)
    {
        // Here we check if the given point lies inside the text areas

        if(checkText(px, py))
            return 0;

        int distance=GeometricDistances.pointToPoint(
                virtualPoint[0].x,virtualPoint[0].y,
                px,py)-Math.min(rx,ry)/2;
        return distance>0?distance:0;
    }

    /** Obtain a string command descripion of the primitive.
        @param extensions true if FidoCadJ extensions to the old FidoCAD format
            should be active.
        @return the FIDOCAD command line.
    */
    public String toString(boolean extensions)
    {
        String s = "PA "+virtualPoint[0].x+" "+virtualPoint[0].y+" "+
            rx+" "+ry+" "+ri+" "+sty+" "+getLayer()+"\n";

        s+=saveText(extensions);
        return s;
    }

    /** Export the primitive on a vector graphic format.
        @param exp the export interface to employ.
        @param cs the coordinate mapping to employ.
        @throws IOException if a problem occurs, such as it is impossible to
            write on the output file.
    */
    public void export(ExportInterface exp, MapCoordinates cs)
        throws IOException
    {
        exportText(exp, cs, -1);
        exp.exportPCBPad(cs.mapX(virtualPoint[0].x,virtualPoint[0].y),
                       cs.mapY(virtualPoint[0].x,virtualPoint[0].y),
                       sty,
                       Math.abs(cs.mapX(virtualPoint[0].x+rx,
                            virtualPoint[0].y+ry)-
                       cs.mapX(virtualPoint[0].x,virtualPoint[0].y)),
                       Math.abs(cs.mapY(virtualPoint[0].x+rx,
                            virtualPoint[0].y+ry)-
                       cs.mapY(virtualPoint[0].x,virtualPoint[0].y)),
                       (int)(ri*cs.getXMagnitude()), getLayer(),drawOnlyPads);
    }
    /** Get the number of the virtual point associated to the Name property
        @return the number of the virtual point associated to the Name property
    */
    public int getNameVirtualPointNumber()
    {
        return 1;
    }

    /** Get the number of the virtual point associated to the Value property
        @return the number of the virtual point associated to the Value property
    */
    public  int getValueVirtualPointNumber()
    {
        return 2;
    }

}